package com.netx.generics.sql;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;
import java.util.NoSuchElementException;
import java.util.logging.Logger;
import java.io.PrintWriter;
import org.apache.commons.pool.impl.GenericObjectPool;
import org.apache.commons.dbcp.DriverManagerConnectionFactory;
import org.apache.commons.dbcp.PoolableConnectionFactory;
import org.apache.commons.dbcp.PoolingDataSource;
import com.netx.generics.basic.Checker;


public class ConnectionPool implements DataSource {

	private final GenericObjectPool _pool;
	private final PoolingDataSource _dataSource;
	private boolean _closed;
	
	public ConnectionPool(String driver, String url, String username, String password) {
		Checker.checkNull(driver, "driver");
		Checker.checkNull(url, "url");
		Checker.checkNull(username, "username");
		Checker.checkNull(password, "password");
		try {
			Class.forName(driver);
		}
		catch(ClassNotFoundException cnfe) {
			throw new IllegalArgumentException(driver+": driver not found");
		}
		_pool = new GenericObjectPool(null);
		_pool.setMaxActive(100);
		_pool.setMaxIdle(100);
		_pool.setWhenExhaustedAction(GenericObjectPool.WHEN_EXHAUSTED_BLOCK);
		_pool.setMaxWait(10*1000);
		_pool.setMinEvictableIdleTimeMillis(60*1000);
		DriverManagerConnectionFactory connectionFactory = new DriverManagerConnectionFactory(url, username, password);
		// TODO configure PreparedStatement pooling
		new PoolableConnectionFactory(connectionFactory, _pool, null, null, false, true);
		_dataSource = new PoolingDataSource(_pool);
		_closed = false;
	}
	
	public Connection getConnection() throws SQLException {
		_checkClosed();
		try {
			return _dataSource.getConnection();
		}
		catch(NoSuchElementException nsee) {
			throw new ConnectionExhaustionException("connection pool is exhausted");
		}
		catch(SQLException sqle) {
			if(sqle.getMessage().indexOf("pool exhausted") != -1) {
				throw new ConnectionExhaustionException("connection pool is exhausted");
			}
			else {
				throw sqle;
			}
		}
	}

	public Connection getConnection(String username, String password) throws SQLException {
		_checkClosed();
		try {
			return _dataSource.getConnection(username, password);
		}
		catch(NoSuchElementException nsee) {
			throw new ConnectionExhaustionException("connection pool is exhausted");
		}
	}
	
	public int getLoginTimeout() {
		_checkClosed();
		return _dataSource.getLoginTimeout();
	}
	
	public PrintWriter getLogWriter() {
		_checkClosed();
		return _dataSource.getLogWriter();
	}
	
	public void setLoginTimeout(int seconds) {
		_checkClosed();
		_dataSource.setLoginTimeout(seconds);
	}
	
	public void setLogWriter(PrintWriter writer) {
		_checkClosed();
		_dataSource.setLogWriter(writer);
	}

	public Logger getParentLogger() throws SQLFeatureNotSupportedException {
		_checkClosed();
		return _dataSource.getParentLogger();
	}

	public int getMaxActive() {
		_checkClosed();
		return _pool.getMaxActive();
	}

	public void setMaxActive(int maxActive) {
		_checkClosed();
		Checker.checkIllegalValue(maxActive, 0, "maxActive");
		_pool.setMaxActive(maxActive);
	}

	public int getMaxIdle() {
		_checkClosed();
		return _pool.getMaxIdle();
	}

	public void setMaxIdle(int maxIdle) {
		_checkClosed();
		_pool.setMaxIdle(maxIdle);
	}

	public int getMaxWait() {
		_checkClosed();
		return (int)_pool.getMaxWait()/1000;
	}

	public void setMaxWait(int maxWait) {
		_checkClosed();
		Checker.checkMinValue(maxWait, 0, "maxWait");
		Checker.checkIllegalValue(maxWait, 0, "maxWait");
		_pool.setMaxWait(maxWait*1000);
	}

	public int getRemoveAbandonedTimeout() {
		_checkClosed();
		return (int)_pool.getMinEvictableIdleTimeMillis()/1000;
	}

	public void setRemoveAbandonedTimeout(int removeAbandonedTimeout) {
		_checkClosed();
		Checker.checkMinValue(removeAbandonedTimeout, 0, "removeAbandonedTimeout");
		Checker.checkIllegalValue(removeAbandonedTimeout, 0, "removeAbandonedTimeout");
		_pool.setMinEvictableIdleTimeMillis(removeAbandonedTimeout*1000);
	}
	
	public int getNumActiveConnections() {
		_checkClosed();
		return _pool.getNumActive();
	}

	public int getNumIdleConnections() {
		_checkClosed();
		return _pool.getNumIdle();
	}
	
	public void close() throws SQLException {
		try {
			if(!_closed) {
				_closed = true;
				_pool.close();
			}
		}
		catch(SQLException sqle) {
			throw sqle;
		}
		catch(RuntimeException re) {
			throw re;
		}
		catch(Exception e) {
			throw new SQLException(e.getMessage());
		}
	}
	
	public boolean isWrapperFor(Class<?> iface) {
		return false;
	}
	
	public <T> T unwrap(Class<T> iface) {
		return null;
	}

	private void _checkClosed() {
		if(_closed) {
			throw new IllegalStateException("this connection pool has already been closed");
		}
	}
}
